using System;
using System.Collections.Generic;
using System.Xml;
using System.Xml.Serialization;
using System.Xml.Xsl;
using System.Text;
using System.Net;
using System.IO;
using System.Reflection;
using System.Collections;

using Sgml;
using WeatherRouter.Model;

namespace WeatherRouter
{
    public class Cache
    {
        private const string CACHE_FOLDER = "Cache";
        private const string DOC_TYPE_HTML = "HTML";

        private string _servicePath;

        private static Cache _instance;

        /// <summary>
        /// Default constructor.
        /// </summary>
        public Cache()
        { }

        /// <summary>
        /// Gets name of cache folder without path.
        /// </summary>
        public string CacheFolder
        {
            get
            {
                return CACHE_FOLDER;
            }
        }

        /// <summary>
        /// Returns the single instance of this object.
        /// </summary>
        public static Cache Instance
        {
            get
            {
                // Lazy initialization.
                if (_instance == null)
                {
                    _instance = new Cache();
                }

                return _instance;
            }
        }

        public static void Init()
        {
            Assembly assembly = Assembly.GetExecutingAssembly();
            string codeBase = assembly.Location;

            Instance._servicePath = Path.GetDirectoryName(codeBase);

            // Ensure cache folder exists			
            if (!Directory.Exists(Instance._servicePath + @"\" + CACHE_FOLDER))
            {
                Directory.CreateDirectory(Instance._servicePath + @"\" + CACHE_FOLDER);
            }
        }

        public void UpdateCache(Feed feed)
        {
            LogManager.Info("Start updating cache for feed " + feed.Name);

            // Update feed last syncronized timestamp
            UpdateFeedTimestamp(feed);

            // Initialize web request
            WebRequest feedRequest = WebRequest.Create(feed.Url);

            // Set proxy credentials
            SetProxyCredentials(feedRequest);

            // Request feed from Web    
            string responseBuffer = GetFeedContent(feedRequest);
            responseBuffer = CanonizeFeed(feed.Schema, feed.Name, responseBuffer);

            SaveFeed(feed.Name, responseBuffer);

            LogManager.Info("Next sync cache - " + feed.NextSync.ToString());
            LogManager.Info("Cached weather service");
        }

        public string GetFeedContent(string feedName)
        {
            string content = "";
            StreamReader reader = null;

            try
            {
                reader = new StreamReader(CACHE_FOLDER + @"\" + feedName + ".cache", Encoding.GetEncoding(1251));
                content = reader.ReadToEnd();
            }
            finally
            {
                reader.Close();
            }

            return content;
        }

        /// <summary>
        /// Updates timestamp of given feed when it was last synchronized.
        /// </summary>
        /// <param name="feed"></param>
        private void UpdateFeedTimestamp(Feed feed)
        {
            feed.LastSynchronized = DateTime.Now;
        }

        /// <summary>
        /// Returns feed content from the Web.
        /// </summary>
        /// <param name="feedRequest">Feed request.</param>
        /// <returns>Feed content.</returns>
        private string GetFeedContent(WebRequest feedRequest)
        {
            string responseBuffer = "";
            StreamReader reader = null;
            WebResponse feedResponse = null;

            try
            {
                feedResponse = feedRequest.GetResponse();
                reader = new StreamReader(feedResponse.GetResponseStream(), Encoding.GetEncoding(1251));
                responseBuffer = reader.ReadToEnd();  
            }
            catch (Exception ex)
            {
                LogManager.Error("Exception during web request to weather service " + ex.ToString());
            }
            finally
            {
                if (feedResponse != null)
                {
                    feedResponse.Close();
                }
                if (reader != null)
                {
                    reader.Close();
                }
            }
            return responseBuffer;
        }

        /// <summary>
        /// Stores feed to persist object in the cache folder.
        /// </summary>
        /// <param name="feedName">Name of the feed.</param>
        /// <param name="responseBuffer">Feed content.</param>
        private void SaveFeed(string feedName, string responseBuffer)
        {
            // And store the web content in the cache            
            StreamWriter writer = null;

            try
            {
                writer = new StreamWriter(CACHE_FOLDER + @"\" + feedName + ".cache");
                writer.Write(responseBuffer);
                writer.Flush();
            }
            catch (IOException ex)
            {
                LogManager.Error("Exception during saving weather response into the file " + ex.ToString());
            }
            finally
            {
                if (writer != null)
                {
                    writer.Close();
                }
            }
        }

        private void SetProxyCredentials(WebRequest feedRequest)
        {
            // Assign proxy credentials to web request, if its enabled   
            if (Configuration.Instance.Proxy != null && Configuration.Instance.Proxy.Enabled)
            {
                WebProxy webProxy = new WebProxy(Configuration.Instance.Proxy.Endpoint);
                feedRequest.Proxy = webProxy;

                if (Configuration.Instance.Proxy.Authentication)
                {
                    NetworkCredential networkCredential = new NetworkCredential(
                        Configuration.Instance.Proxy.Username,
                        Configuration.Instance.Proxy.Password,
                        Configuration.Instance.Proxy.Domain
                        );
                    feedRequest.Credentials = networkCredential;
                }
            }
        }
        
        /// <summary>
        /// Converts feed content to canonical format. First, html content converted to xHtml,
        /// second, xHtml transformed using feed schema.
        /// </summary>
        /// <param name="schemaName">Name of the schema.</param>
        /// <param name="feedName">Feed name.</param>
        /// <param name="feedContent">Feed content.</param>
        /// <returns>Canonized feed content.</returns>
        private string CanonizeFeed(string schemaName, string feedName, string feedContent)
        {
            SgmlReader sgmlReader = null;
            StringReader stringReader = null;
            MemoryStream writerStream = null;
            XmlTextWriter xmlWriter = null;
            StreamReader sr = null;
            // MvpXslTransform xsl = null;
            XslCompiledTransform xsl = null;
            MemoryStream canonizedStream = null;
            string canonized = "";

            try
            {
                stringReader = new StringReader(feedContent);

                sgmlReader = new SgmlReader();
                sgmlReader.DocType = DOC_TYPE_HTML;
                sgmlReader.InputStream = stringReader;

                // Convert html to xHtml.
                writerStream = new MemoryStream();
                xmlWriter = new XmlTextWriter(writerStream, Encoding.GetEncoding(1251));
                xmlWriter.WriteStartDocument();
                sgmlReader.Read();
                while (!sgmlReader.EOF)
                {
                    xmlWriter.WriteNode(sgmlReader, true);
                }

                xmlWriter.Flush();

                // Rewind writer stream to read from it from the beginning.
                writerStream.Seek(0, SeekOrigin.Begin);
                sr = new StreamReader(writerStream, Encoding.GetEncoding(1251));

                // Transform xHtml to canonical form using feed schema.
                xsl = new XslCompiledTransform();
                canonizedStream = new MemoryStream();
                xsl.Load(CACHE_FOLDER + @"\" + schemaName, XsltSettings.TrustedXslt, new XmlUrlResolver());
                // xsl.Transform(new XmlInput(sr), null, new XmlOutput(canonizedStream));
                xsl.Transform(new XmlTextReader(sr), null, new XmlTextWriter(canonizedStream, Encoding.GetEncoding(1251)));
                sr.Close();

                // Read canonical xml to string.
                canonizedStream.Seek(0, SeekOrigin.Begin);
                sr = new StreamReader(canonizedStream);
                canonized = sr.ReadToEnd();
            }
            finally
            {
                if (sgmlReader != null)
                {
                    sgmlReader.Close();
                }
                if (stringReader != null)
                {
                    stringReader.Close();
                }
                if (writerStream != null)
                {
                    writerStream.Close();    
                }
                if (xmlWriter != null)
                {
                    xmlWriter.Close();
                }
                if (sr != null)
                {
                    sr.Close();
                }
                if (canonizedStream != null)
                {
                    canonizedStream.Close();
                }
            }
            
            return canonized;
        }  
    }
}
